#ifndef PEERCAST_H_
#define PEERCAST_H_

#include <gtkmm.h>
#include <boost/ptr_container/ptr_list.hpp>

#include "conf/element.h"
#include "yellowpage.h"
#include "loader.h"
#include "gtkcellrendererrelay.h"
#include "widgets.h"
#include "menu.h"
#include "panel.h"

namespace gpyp {

class Application;

namespace peercast {

enum Status {
  STATUS_INVALID,
  STATUS_NONE,
  STATUS_WAIT,
  STATUS_CONNECT,
  STATUS_REQUEST,
  STATUS_CLOSE,
  STATUS_RECEIVE,
  STATUS_BROADCAST,
  STATUS_ABORT,
  STATUS_SEARCH,
  STATUS_NOHOSTS,
  STATUS_IDLE,
  STATUS_ERROR,
  STATUS_NOTFOUND
};

class FoundChannel;
typedef boost::shared_ptr<FoundChannel> RefFoundChannel;
struct RelayedChannel : boost::noncopyable {
  RefChannel ch_;
  RefFoundChannel fc_;

  /* channel stuff */
  int uptime_;
  int skips_;
  int bcflags_;

  /* relay stuff */
  int hosts_;

  /* CRCKYL_EX */
  int firewalled_;
  int relay_;
  int tracker_;
  int keep_;

  /* gpyp data */
  Status status_;

  int  total_listeners_;
  int  total_relays_;

  inline RelayedChannel() :
    uptime_         (-1),
    skips_          (-1),
    bcflags_        (-1),
    hosts_          (-1),
    firewalled_     (-1),
    relay_          (1),
    tracker_        (-1),
    keep_           (-1),
    status_         (STATUS_INVALID),
    total_listeners_(-1),
    total_relays_   (-1)
  {
  }
};
typedef boost::shared_ptr<RelayedChannel> RefRelayedChannel;

struct Host : boost::noncopyable {
  std::string ip_;
  std::string host_;
  int         port_;
  int         hops_;
  int         listeners_;
  int         relays_;
  int         uptime_;
  int         push_;
  int         relay_;
  int         direct_;
  int         cin_;
  int         stable_;
  std::string version_;
  int         versionvp_;
  int         update_;
  int         tracker_;

  /* CRCKYL_EX */
  int         servid_;

  inline Host() :
    port_     (-1),
    hops_     (-1),
    listeners_(-1),
    relays_   (-1),
    uptime_   (-1),
    push_     (-1),
    relay_    (-1),
    direct_   (-1),
    cin_      (-1),
    stable_   (-1),
    versionvp_(-1),
    update_   (-1),
    tracker_  (-1),
    servid_   (-1)
  {
  }
};

struct FoundChannel : boost::noncopyable {
  typedef boost::ptr_list<Host>::iterator iterator;

  ID id_;

  /* hits stuff */
  int hosts_;
  int listeners_;
  int relays_;
  int firewalled_;
  int closest_;
  int furthest_;
  int newest_;

  boost::ptr_list<Host> host_list_;

  inline FoundChannel() { }
};

struct Servent : boost::noncopyable {
  RefFoundChannel fc_;

  int         servid_;
  ID          id_;
  std::string ip_;
  std::string host_;
  int         port_;
  std::string type_;
  std::string status_;
  time_t      lastconnect_;
  std::string agent_;
  time_t      lastskiptime_;
  int         lastskipcount_;

  inline Servent() :
    servid_       (-2),
    port_         (-1),
    lastconnect_  (0),
    lastskiptime_ (0),
    lastskipcount_(-1)
  {
  }
};
typedef boost::shared_ptr<Servent> RefServent;

class Parser : public YPXMLParserBase {
public:
  typedef std::list<RefRelayedChannel> RelayedList;
  typedef IDHashTable<FoundChannel> FoundHash;
  typedef std::list<RefServent> ServentList;

  typedef RelayedList::iterator iterator;

private:
  Application &app_;

  RefRelayedChannel ch_relayed_;
  RefFoundChannel   ch_found_;
  Host             *ch_found_host_;
  RefServent        ch_servent_;

  RelayedList relayed_;
  FoundHash   found_;
  ServentList servents_;

  bool has_servents_;

protected:
  virtual void on_start_parsing();
  virtual void on_complete_parsing();
  virtual void on_error(GError *error);

private:
#define CH_INT_HANDLER(CH, NS, NAME)                            \
  inline void onattr_##NS##_##NAME(const char *value) {         \
    ch_##CH##_->NAME##_ = toint(value, -1);                     \
  }
#define CH_STR_HANDLER(CH, NS, NAME)                            \
  inline void onattr_##NS##_##NAME(const char *value) {         \
    ch_##CH##_->NAME##_ = value;                                \
  }
  CH_INT_HANDLER(relayed, relayed_ch, uptime);
  CH_INT_HANDLER(relayed, relayed_ch, skips);
  CH_INT_HANDLER(relayed, relayed_ch, bcflags);

  CH_INT_HANDLER(relayed, relayed_ch_relay, hosts);
  /* CRCKYL_EX */
  CH_INT_HANDLER(relayed, relayed_ch_relay, firewalled);
  CH_INT_HANDLER(relayed, relayed_ch_relay, relay);
  CH_INT_HANDLER(relayed, relayed_ch_relay, tracker);
  CH_INT_HANDLER(relayed, relayed_ch_relay, keep);

  CH_INT_HANDLER(found, found_ch_hits, hosts);
  CH_INT_HANDLER(found, found_ch_hits, listeners);
  CH_INT_HANDLER(found, found_ch_hits, relays);
  CH_INT_HANDLER(found, found_ch_hits, firewalled);
  CH_INT_HANDLER(found, found_ch_hits, closest);
  CH_INT_HANDLER(found, found_ch_hits, furthest);
  CH_INT_HANDLER(found, found_ch_hits, newest);

  inline void onattr_found_ch_hits_host_ip(const char *value) {
    ch_found_host_->ip_ = value;
    std::string::size_type p = ch_found_host_->ip_.rfind(':');
    if (p != std::string::npos) {
      std::string port(ch_found_host_->ip_, p + 1);
      if ((ch_found_host_->port_ = toint(port, -1)) >= 0) {
        ch_found_host_->host_.assign(ch_found_host_->ip_, 0, p);
      } else {
        ch_found_host_->host_ = ch_found_host_->ip_;
      }
    }
  }
  CH_INT_HANDLER(found_host, found_ch_hits_host, hops);
  CH_INT_HANDLER(found_host, found_ch_hits_host, listeners);
  CH_INT_HANDLER(found_host, found_ch_hits_host, relays);
  CH_INT_HANDLER(found_host, found_ch_hits_host, uptime);
  CH_INT_HANDLER(found_host, found_ch_hits_host, push);
  CH_INT_HANDLER(found_host, found_ch_hits_host, relay);
  CH_INT_HANDLER(found_host, found_ch_hits_host, direct);
  CH_INT_HANDLER(found_host, found_ch_hits_host, cin);
  CH_INT_HANDLER(found_host, found_ch_hits_host, stable);
  CH_STR_HANDLER(found_host, found_ch_hits_host, version);
  CH_INT_HANDLER(found_host, found_ch_hits_host, versionvp);
  CH_INT_HANDLER(found_host, found_ch_hits_host, update);
  CH_INT_HANDLER(found_host, found_ch_hits_host, tracker);
  /* CRCKYL_EX */
  CH_INT_HANDLER(found_host, found_ch_hits_host, servid);

  inline void onattr_servent_servid(const char *value) {
    ch_servent_->servid_ = toint(value, -2);
  }
  inline void onattr_servent_id(const char *value) {
    ch_servent_->id_.set(value);
  }
  inline void onattr_servent_ip(const char *value) {
    ch_servent_->ip_ = value;
    std::string::size_type p = ch_servent_->ip_.rfind(':');
    if (p != std::string::npos) {
      std::string port(ch_servent_->ip_, p + 1);
      if ((ch_servent_->port_ = toint(port, -1)) >= 0) {
        ch_servent_->host_.assign(ch_servent_->ip_, 0, p);
      } else {
        ch_servent_->host_ = ch_servent_->ip_;
      }
    }
  }
  CH_STR_HANDLER(servent, servent, type);
  CH_STR_HANDLER(servent, servent, status);
  inline void onattr_servent_lastconnect(const char *value) {
    ch_servent_->lastconnect_ = touint(value, 0);
  }
  CH_STR_HANDLER(servent, servent, agent);
  inline void onattr_servent_lastskiptime(const char *value) {
    ch_servent_->lastskiptime_ = touint(value, 0);
  }
  CH_INT_HANDLER(servent, servent, lastskipcount);
#undef CH_INT_HANDLER
#undef CH_STR_HANDLER

  void onload_relayed_ch(const AttributesWrapper &attributes);
  void unload_relayed_ch();

  void onload_found_ch(const AttributesWrapper &attributes);
  void onload_found_ch_hits_host(const AttributesWrapper &attributes);
  void unload_found_ch();

  void onload_servents(const AttributesWrapper &attributes);
  void onload_servents_servent(const AttributesWrapper &attributes);

public:
  inline iterator begin() {
    return relayed_.begin();
  }
  inline iterator end() {
    return relayed_.end();
  }

  inline ServentList &get_servents() {
    return servents_;
  }

  inline bool get_has_servents() const {
    return has_servents_;
  }

  Parser(Application &app);
};

}/*peercast*/

class PCRelayViewElement : public panel::PagePanedElement {
private:
  conf::RefAttributeBool connected_;
  conf::RefAttributeUint update_interval_;

public:
  inline conf::RefAttributeBool connected() {
    return connected_;
  }
  inline conf::RefAttributeUint update_interval() {
    return update_interval_;
  }

  inline PCRelayViewElement(const Glib::ustring &name, bool enable) :
    panel::PagePanedElement(name, enable),
    connected_             ("connected", false),
    update_interval_       ("update_interval", 3)
  {
    add(connected_);
    add(update_interval_);
  }
};
typedef boost::shared_ptr<PCRelayViewElement> RefPCRelayViewElement;

class PCRelayView : public panel::Page<Gtk::VBox>,
                    public panel::Paned {
public:
  typedef peercast::RefRelayedChannel RefRelayedChannel;
  typedef peercast::Host Host;
  typedef peercast::FoundChannel FoundChannel;
  typedef peercast::RefFoundChannel RefFoundChannel;
  typedef peercast::RefServent RefServent;
  typedef peercast::Parser Parser;

  static const Gdk::Color RELAY_COLOR_GREEN;
  static const Gdk::Color RELAY_COLOR_BLUE;
  static const Gdk::Color RELAY_COLOR_PURPLE;
  static const Gdk::Color RELAY_COLOR_ORANGE;
  static const Gdk::Color RELAY_COLOR_RED;
  static const Gdk::Color RELAY_COLOR_WHITE;
  static const Gdk::Color RELAY_COLOR_BLACK;

  static const Gdk::Color TEXT_COLOR_ORANGE;
  static const Gdk::Color TEXT_COLOR_GREEN;
  static const Gdk::Color TEXT_COLOR_BLACK;

  inline static const Gdk::Color &get_relay_color(bool available,
                                                  int firewalled,
                                                  int relays,
                                                  int relay) {
    return (available
            ? (firewalled > 0
               ? (relays > 0
                  ? RELAY_COLOR_ORANGE
                  : RELAY_COLOR_RED)
               : (relay > 0
                  ? RELAY_COLOR_GREEN
                  : (relays > 0
                     ? RELAY_COLOR_BLUE
                     : RELAY_COLOR_PURPLE)))
            : RELAY_COLOR_BLACK);
  }

  class Relays : public WidgetInput<TreeView> {
  private:
    Application &app_;

    Gtk::TreeModelColumn<RefRelayedChannel> model_column_channel_;
    Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > model_column_status_icon_;
    Gtk::TreeModelColumnRecord   columns_;
    Glib::RefPtr<Gtk::ListStore> liststore_;

    GtkCellRenderer        *renderer_name_;
    Gtk::CellRendererPixbuf renderer_status_icon_;
    Gtk::CellRendererText   renderer_other_;

    GtkTreeViewColumn *column_name_;
    GtkTreeViewColumn *column_bitrate_;
    GtkTreeViewColumn *column_status_;
    GtkTreeViewColumn *column_listeners_;
    GtkTreeViewColumn *column_keep_;

    Menu popup_menu_;

    GHashTable *status_icon_hash_;

  protected:
    virtual void prepare_commandopts(CommandOpts &opts);
    virtual bool on_popup(guint button, guint32 activate_time);

#ifndef GTK_CELL_LAYOUT_HAVE_GET_CELLS
    virtual GtkCellRenderer *get_tooltip_renderer(GtkTreeViewColumn *column);
#endif

  private:
    void cell_data_common(GtkCellRenderer  *cell,
                          RefRelayedChannel rc);
    static void _cell_data_name(GtkTreeViewColumn *tree_column,
                                GtkCellRenderer   *cell,
                                GtkTreeModel      *tree_model,
                                GtkTreeIter       *iter,
                                Relays            *self);
    static void _cell_data_bitrate(GtkTreeViewColumn *tree_column,
                                   GtkCellRenderer   *cell,
                                   GtkTreeModel      *tree_model,
                                   GtkTreeIter       *iter,
                                   Relays            *self);
    static void _cell_data_status(GtkTreeViewColumn *tree_column,
                                  GtkCellRenderer   *cell,
                                  GtkTreeModel      *tree_model,
                                  GtkTreeIter       *iter,
                                  Relays            *self);
    static void _cell_data_listeners(GtkTreeViewColumn *tree_column,
                                     GtkCellRenderer   *cell,
                                     GtkTreeModel      *tree_model,
                                     GtkTreeIter       *iter,
                                     Relays            *self);
    static void _cell_data_keep(GtkTreeViewColumn *tree_column,
                                GtkCellRenderer   *cell,
                                GtkTreeModel      *tree_model,
                                GtkTreeIter       *iter,
                                Relays            *self);

    static void status_icon_destroy(Glib::RefPtr<Gdk::Pixbuf> *icon);
    Glib::RefPtr<Gdk::Pixbuf> get_status_icon(peercast::Status status);

  public:
    void update(Parser &parser);

    inline RefRelayedChannel get_selected() {
      Gtk::TreeIter iter = get_selection()->get_selected();
      if (iter) {
        return iter->get_value(model_column_channel_);
      } else {
        return RefRelayedChannel();
      }
    }

    Relays(Application &app);
    virtual ~Relays();
  };
  class Connections : public WidgetInput<TreeView> {
  private:
    Application      &app_;
    RefRelayedChannel ch_;

    Gtk::TreeModelColumn<RefServent>    model_column_servent_;
    Gtk::TreeModelColumn<Gdk::Color>    model_column_color_relay_;
    Gtk::TreeModelColumn<bool>          model_column_nabla_;
    Gtk::TreeModelColumn<Glib::ustring> model_column_text_;
    Gtk::TreeModelColumnRecord          columns_;
    Glib::RefPtr<Gtk::ListStore>        liststore_;
    Glib::RefPtr<Gtk::TreeModelFilter>  filter_;

    GtkCellRenderer   *renderer_;
    GtkTreeViewColumn *column_;

    Menu popup_menu_;

  protected:
    virtual void prepare_commandopts(CommandOpts &opts);
    virtual bool on_popup(guint button, guint32 activate_time);

#ifndef GTK_CELL_LAYOUT_HAVE_GET_CELLS
    virtual GtkCellRenderer *get_tooltip_renderer(GtkTreeViewColumn *column);
#endif

  private:
    bool visible_func(const Gtk::TreeIter &iter);

  public:
    void update(Parser &parser);

    void set(RefRelayedChannel ch);

    inline RefServent get_selected() {
      Gtk::TreeIter iter = get_selection()->get_selected();
      if (iter) {
        return iter->get_value(model_column_servent_);
      } else {
        return RefServent();
      }
    }

    Connections(Application &app);
  };

  class ConnectPage : public Gtk::HBox {
  private:
    Gtk::Label  l_message_;
    Gtk::Label  l_host_;
    HostEntry   d_host_;
    Gtk::Label  l_password_;
    Gtk::Entry  d_password_;
    Gtk::Button connect_;

  public:
    inline Glib::SignalProxy0<void> signal_connected() {
      return connect_.signal_clicked();
    }

    inline void set_message(const Glib::ustring &message) {
      l_message_.set_label(message);
    }

    ConnectPage(Application &app);
  };

private:
  Application           &app_;
  RefPCRelayViewElement  conf_;

  ConnectPage            connect_page_;

  Relays                 relays_;
  Connections            connections_;
  //Gtk::HButtonBox        buttons_left_;
  //Gtk::HButtonBox        buttons_right_;
  Gtk::VBox              box_left_;
  Gtk::VBox              box_right_;
  Gtk::HPaned           *pane_;

  loader::Pool           loader_;
  loader::RefConnection  loader_connection_;
  Parser                 parser_;

  sigc::connection       conn_channel_selected_;
  sigc::connection       conn_update_timeout_;

  size_t                 error_count_;

  BuiltinCommand        &bc_pc_;
  BuiltinCommand        &bc_pc_relay_;
  BuiltinCommand        &bc_pc_connection_;

protected:
  inline virtual void on_presave() {
    panel::Page<Gtk::VBox>::on_presave();
    panel::Paned::on_presave();
  }

  inline virtual void on_map() {
    //panel::Page<Gtk::VBox>::on_map();
    Gtk::VBox::on_map();
    panel::Paned::on_map();
  }
  inline virtual void on_unmap() {
    panel::Paned::on_unmap();
    Gtk::VBox::on_unmap();
    //panel::Page<Gtk::VBox>::on_unmap();
  }

private:
  void loader_on_complete(const loader::RefResponse response);
  void loader_on_error(const loader::RefError error);

  void set_update_timer();
  bool update_timeout();

  void relays_selection_on_changed();
  //void connections_selection_on_changed();

  void request_channel_command(RefRelayedChannel rc, const std::string &command);
  void request_channel_command(const std::string &command);
  void request_connection_disconnect(RefServent s);

  void channel_bump();
  void channel_disconnect();
  void channel_keep();
  void connection_disconnect();

  void show_connect_page(const Glib::ustring &message);

public:
  void activate();
  void deactivate();

private:
  bool init_idle();

  bool bc_pc_activate(ShellLine &line, CommandOpts &opts);
  bool bc_pc_deactivate(ShellLine &line, CommandOpts &opts);

  bool bc_pc_relay_bump      (ShellLine &line, CommandOpts &opts);
  bool bc_pc_relay_disconnect(ShellLine &line, CommandOpts &opts);
  bool bc_pc_relay_keep      (ShellLine &line, CommandOpts &opts);

  bool bc_pc_connection_disconnect(ShellLine &line, CommandOpts &opts);

public:
  PCRelayView(Application &app, RefPCRelayViewElement conf);
  virtual ~PCRelayView();
};

}/*gpyp*/

#endif/*PEERCAST_H_*/
